import os
import shutil
import codecs
import re
from openpyxl import load_workbook
from openpyxl.worksheet.table import Table
from openpyxl.styles import Alignment
from enum import Enum, auto
from datetime import datetime

g_matrix_path = "./../../execltool/can_matrix/matrix_files"
g_pdur_path   = "./../../execltool/route_table"

# Define file info structure
class can_file_info:
    def __init__(self, cover_name, rm_rev, rm_date, rm_author, rm_change_comment, can_tag):
        self.cover_name = cover_name
        self.rm_rev = rm_rev
        self.rm_date = rm_date
        self.rm_author = rm_author
        self.rm_change_comment = rm_change_comment
        self.can_tag = can_tag

# Define struct
class Controller(Enum):
    CanController_BCAN = 0          # BCAN
    CanController_CCAN = auto()     # CCAN
    CanController_PCAN = auto()     # PCAN
    CanController_ACAN = auto()     # ADBCANFD
    CanController_DCAN = auto()     # OBDCAN
    # CanController_CAN5 = auto()

class gl0906_can_controller_hw_obj_num:
    def __init__(self):
        # Define CAN HW object number
        self.can_hw_obj_num_0 = {'send': 48, 'receive': 48}
        self.can_hw_obj_num_1 = {'send': 32, 'receive': 32}
        self.can_hw_obj_num_2 = {'send': 32, 'receive': 32}
        self.can_hw_obj_num_3 = {'send': 4, 'receive': 3}
        self.can_hw_obj_num_4 = {'send': 16, 'receive': 16}
        # self.can_hw_obj_num_5 = {'send': 16, 'receive': 16}

        self.cntl_hon_dict = {
            Controller.CanController_BCAN: self.can_hw_obj_num_0,
            Controller.CanController_CCAN: self.can_hw_obj_num_1,
            Controller.CanController_PCAN: self.can_hw_obj_num_2,
            Controller.CanController_ACAN: self.can_hw_obj_num_3,
            Controller.CanController_DCAN: self.can_hw_obj_num_4,
        }

class gl0906_can_hw_obj_filter:
    def __init__(self, id, code, mask):
        self.id = id
        self.code = code
        self.mask = mask

# Define communication matrix structure
# The time is in milliseconds
class gl0906_can_message_info:
    def __init__(self, msg_name, msg_type, msg_id, send_type, cycle_time, msg_length, direction):
        self.msg_name = msg_name            # Msg Name
        self.msg_type = msg_type            # Msg Type: Normal, NM, Diag
        self.msg_id = msg_id                # Message Identifier
        self.send_type = send_type          # Send type:"Cycle", "Event", "IfActive", "CE" and "CA
        self.cycle_time = cycle_time        # Msg Cycle Time
        self.msg_length = msg_length        # Byte length of the message
        self.direction = direction          # S: Send, R: Receive

class gl0906_can_parse:
    def __init__(self) -> None:
        self.can_send_messages = {
            Controller.CanController_BCAN: [],
            Controller.CanController_CCAN: [],
            Controller.CanController_PCAN: [],
            Controller.CanController_ACAN: [],
            Controller.CanController_DCAN: []
        }
        self.can_pdur_messages = {
            Controller.CanController_BCAN: [],
            Controller.CanController_CCAN: [],
            Controller.CanController_PCAN: [],
            Controller.CanController_ACAN: [],
            Controller.CanController_DCAN: []
        }
        self.can_hw_obj_filter = {
            Controller.CanController_BCAN: [],
            Controller.CanController_CCAN: [],
            Controller.CanController_PCAN: [],
            Controller.CanController_ACAN: [],
            Controller.CanController_DCAN: []
        }

        self.cntl_hond = gl0906_can_controller_hw_obj_num()

    # Get CAN Communication Matrix Data
    def get_can_comm_matrix_data(self, wb, Controller):
        # Get sheet
        ws = wb["Matrix"]
        # print("We will read sheet:", ws.title)

        for row in ws.iter_rows(min_row=2, values_only=True):
            if row[0] == None or row[34] == "S":
                continue
            is_append = True
            for msg in self.can_send_messages[Controller]:
                if msg.msg_name == row[0] or msg.msg_id == row[2].rstrip('x'):
                    print(f"{Controller.name}: msg_name = {msg.msg_name} or msg_id = {msg.msg_id}, ignore")
                    is_append = False
                    break
            if is_append == False:
                continue
            self.can_send_messages[Controller].append(gl0906_can_message_info(row[0], row[1], row[2].rstrip('x'), row[4], row[5], row[8], row[34]))
    # Parse CAN communication matrix file
    def parse_can_commm_matrix_file(self, filename, Controller):
        if False == os.path.exists(filename):
            print(filename, "file not exists")
            return

        # Read excel file
        wb = load_workbook(filename, read_only=True)
        self.get_can_comm_matrix_data(wb, Controller)

    # Find and parse can comm matrix file
    def find_and_parse_can_comm_matrix_file(self):
        martix_files = os.listdir(g_matrix_path)

        # Filter files whose names contain "FBCM"
        martix_file_names = [f for f in martix_files if f.endswith('.xlsx') and any(tag in f for tag in ['FBCM_A_', 'FBCM_B_', 'FBCM_C_', 'FBCM_P_'])]
        print(martix_file_names)

        # Handle each file
        for filename in martix_file_names:
            if '_A_' in filename:
                self.parse_can_commm_matrix_file(g_matrix_path + '/' + filename, Controller.CanController_ACAN)
            elif '_B_' in filename:
                self.parse_can_commm_matrix_file(g_matrix_path + '/' + filename, Controller.CanController_BCAN)
            elif '_C_' in filename:
                self.parse_can_commm_matrix_file(g_matrix_path + '/' + filename, Controller.CanController_CCAN)
            elif '_P_' in filename:
                self.parse_can_commm_matrix_file(g_matrix_path + '/' + filename, Controller.CanController_PCAN)
    
    def find_and_parse_can_pdur_table(self):
        wb = load_workbook(filename = g_pdur_path + '/routing_table.xlsx', read_only=True)

        # Parse routing table
        ws = wb["MessageRoutingTable"]
        print(ws.title)

        src_bus = Controller.CanController_PCAN.value
        dst_bus = Controller.CanController_CCAN.value
        for row in ws.iter_rows(min_row=1, values_only=True):
            # Parse source
            if row[0] == "requested signals, source:PCAN":
                src_bus = Controller.CanController_PCAN.value
                controller = Controller.CanController_PCAN
            elif row[0] == "requested signals, source:CCAN":
                src_bus = Controller.CanController_CCAN.value
                controller = Controller.CanController_CCAN
            elif row[0] == "requested signals, source:BCAN":
                src_bus = Controller.CanController_BCAN.value
                controller = Controller.CanController_BCAN
            elif row[0] == "requested signals, source:ACAN":
                src_bus = Controller.CanController_ACAN.value
                controller = Controller.CanController_ACAN
            elif row[0] == "requested signals, source:OBD":
                src_bus = Controller.CanController_DCAN.value
                controller = Controller.CanController_DCAN
            
            # Parse destination
            if row[3] == "distributed messages, target:CCAN":
                dst_bus = Controller.CanController_CCAN.value
                continue
            elif row[3] == "distributed messages, target:BCAN":
                dst_bus = Controller.CanController_BCAN.value
                continue
            elif row[3] == "distributed messages, target:ACAN":
                dst_bus = Controller.CanController_ACAN.value
                continue
            elif row[3] == "distributed messages, target:OBD":
                dst_bus = Controller.CanController_DCAN.value
                continue
            elif row[3] == "distributed messages, target:PCAN":
                dst_bus = Controller.CanController_PCAN.value
                continue

            # Parse message ID
            try:
                row_2_2 = row[2][:2]
            except TypeError as e:
                print(e)
                print(row)
                continue
            if row_2_2 == "0x":
                if row[2] != row[5]:    # src id != dst id, base on the src id
                    print("Message ID do not match")
                    print(row)
                    # continue
                # drop the x
                smid = row[2].rstrip('x')

                # convert to decimal
                id = int(smid, 16)
                
                is_append = True
                for msg in self.can_send_messages[controller]:
                    if msg.msg_name == row[0] or msg.msg_id == row[2].rstrip('x'):
                        print(f"{controller.name}: msg_name = {msg.msg_name} or msg_id = {msg.msg_id}, ignore")
                        is_append = False
                        break
                if is_append == False:
                    continue
                self.can_send_messages[controller].append(gl0906_can_message_info(row[1], None, row[2].rstrip('x'), row[10], row[11], row[9], 'R'))
    
    # BCAN is Can0 that have 48 hardware objects for send message
    def generate_bcan_hw_obj_filter_by_msg(self):
        for msg in self.can_send_messages[Controller.CanController_BCAN]:
            if msg.msg_id[:6] == '0x18DA':
                if msg.msg_id == '0x18DAFAE3':    # 0x18DA0000~0x18DAFFFF ignore
                    self.can_hw_obj_filter[Controller.CanController_BCAN].append(gl0906_can_hw_obj_filter(msg.msg_id, msg.msg_id, '0x1fff0000'))
            else:
                self.can_hw_obj_filter[Controller.CanController_BCAN].append(gl0906_can_hw_obj_filter(msg.msg_id, msg.msg_id, '0x1fffffff'))
        self.can_hw_obj_filter[Controller.CanController_BCAN].append(gl0906_can_hw_obj_filter('0x18DBFFFA', '0x18DBFFFA', '0x1fff0000'))
        self.can_hw_obj_filter[Controller.CanController_BCAN].append(gl0906_can_hw_obj_filter('0x18FFA000', '0x18FFA000', '0x1fffff00'))
        # Fill in the excess MBs
        b_len = len(self.can_hw_obj_filter[Controller.CanController_BCAN])
        for i in range(b_len, self.cntl_hond.cntl_hon_dict[Controller.CanController_BCAN]['receive']):
            self.can_hw_obj_filter[Controller.CanController_BCAN].append(gl0906_can_hw_obj_filter('0x0CFF5805', '0x0CFF5805', '0x1fffffff'))

    # CCAN is Can1 that have 32 hardware objects for send message
    def generate_ccan_hw_obj_filter_by_msg(self):
        for msg in self.can_send_messages[Controller.CanController_CCAN]:
            self.can_hw_obj_filter[Controller.CanController_CCAN].append(gl0906_can_hw_obj_filter(msg.msg_id, msg.msg_id, '0x1fffffff'))
        # Fill in the excess MBs
        for i in range(len(self.can_send_messages[Controller.CanController_CCAN]), self.cntl_hond.cntl_hon_dict[Controller.CanController_CCAN]['receive']):
            self.can_hw_obj_filter[Controller.CanController_CCAN].append(gl0906_can_hw_obj_filter('0x0CFF4713', '0x0CFF4713', '0x1fffffff'))

    # PCAN is Can2 that have 32 hardware objects for send message
    def generate_pcan_hw_obj_filter_by_msg(self):
        for msg in self.can_send_messages[Controller.CanController_PCAN]:
            if re.match(r'0x18FF1D0[89]', msg.msg_id):
                if msg.msg_id == '0x18FF1D08':    # 0x18FF1D09 ignore
                    self.can_hw_obj_filter[Controller.CanController_PCAN].append(gl0906_can_hw_obj_filter(msg.msg_id, msg.msg_id, '0x1ffffff6'))
            elif re.match(r'0x1CFF1[EF]03', msg.msg_id):
                if msg.msg_id == '0x1CFF1E03':    # 0x1CFF1F03 ignore
                    self.can_hw_obj_filter[Controller.CanController_PCAN].append(gl0906_can_hw_obj_filter(msg.msg_id, msg.msg_id, '0x1ffffeff'))
            elif re.match(r'0x18FF[0-9A-F][2-9A-F]F3', msg.msg_id):
                if msg.msg_id == '0x18FF3DF3':    # 0x18FF1E03 ignore
                    self.can_hw_obj_filter[Controller.CanController_PCAN].append(gl0906_can_hw_obj_filter(msg.msg_id, msg.msg_id, '0x1fff00ff'))
            elif re.match(r'0x18FF[0-9A-Fa-f][0-9A-Fa-f]E5', msg.msg_id):   # 0x18FFxxE5 ignore
                if msg.msg_id == '0x18FF70E5':
                    self.can_hw_obj_filter[Controller.CanController_PCAN].append(gl0906_can_hw_obj_filter(msg.msg_id, msg.msg_id, '0x1fff00ff'))
            elif re.match(r'0x18DAFAE[0-9A-F]', msg.msg_id):
                if msg.msg_id == '0x18DAFAE9':
                    self.can_hw_obj_filter[Controller.CanController_PCAN].append(gl0906_can_hw_obj_filter(msg.msg_id, msg.msg_id, '0x1ffffff0'))
            elif re.match(r'0x1CFF[0-9][0-9A-F]F3', msg.msg_id):
                if msg.msg_id == '0x1CFF06F3':
                    self.can_hw_obj_filter[Controller.CanController_PCAN].append(gl0906_can_hw_obj_filter(msg.msg_id, msg.msg_id, '0x1fff7bff'))
            elif re.match(r'0x18DAFA0[0-9]', msg.msg_id):
                if msg.msg_id == '0x18DAFA08':
                    self.can_hw_obj_filter[Controller.CanController_PCAN].append(gl0906_can_hw_obj_filter(msg.msg_id, msg.msg_id, '0x1ffffff0'))
            else:
                self.can_hw_obj_filter[Controller.CanController_PCAN].append(gl0906_can_hw_obj_filter(msg.msg_id, msg.msg_id, '0x1fffffff'))
    # ACAN is Can3 FD that have 3 hardware objects for send message
    def generate_acan_hw_obj_filter_by_msg(self):
        for msg in self.can_send_messages[Controller.CanController_ACAN]:
            if msg.msg_id == '0x18FF5083':
                self.can_hw_obj_filter[Controller.CanController_ACAN].append(gl0906_can_hw_obj_filter(msg.msg_id, msg.msg_id, '0x1fffffff'))
            elif msg.msg_id == '0x233':
                self.can_hw_obj_filter[Controller.CanController_ACAN].append(gl0906_can_hw_obj_filter(msg.msg_id, msg.msg_id, '0x1fffffff'))

        # Other messages
        self.can_hw_obj_filter[Controller.CanController_ACAN].append(gl0906_can_hw_obj_filter('0x18FFA000', '0x18FFA000', '0x1fffff00'))
        # self.can_hw_obj_filter[Controller.CanController_ACAN].append(gl0906_can_hw_obj_filter('0', '0', '0'))

    # OBD is Can4 that have 16 hardware objects for send message
    def generate_dcan_hw_obj_filter_by_msg(self):
        for msg in self.can_send_messages[Controller.CanController_DCAN]:
            if re.match(r'0x18DA', msg.msg_id):
                if msg.msg_id == '0x18DA08FA':
                    self.can_hw_obj_filter[Controller.CanController_DCAN].append(gl0906_can_hw_obj_filter(msg.msg_id, msg.msg_id, '0x1fff0000'))
            elif re.match(r'0x18DB', msg.msg_id):
                if msg.msg_id == '0x18DBFFFA':
                    self.can_hw_obj_filter[Controller.CanController_DCAN].append(gl0906_can_hw_obj_filter(msg.msg_id, msg.msg_id, '0x1fff0000'))
            else:
                self.can_hw_obj_filter[Controller.CanController_DCAN].append(gl0906_can_hw_obj_filter(msg.msg_id, msg.msg_id, '0x1fffffff'))
        # Fill in the excess MBs
        d_len = len(self.can_hw_obj_filter[Controller.CanController_DCAN])
        for i in range(d_len, self.cntl_hond.cntl_hon_dict[Controller.CanController_DCAN]['receive'] - 1):
            self.can_hw_obj_filter[Controller.CanController_DCAN].append(gl0906_can_hw_obj_filter('0x18DA08FA', '0x18DA08FA', '0x1fff0000'))
        # Add the ccp message filter
        self.can_hw_obj_filter[Controller.CanController_DCAN].append(gl0906_can_hw_obj_filter('0x510', '0x510', '0x3ff'))
    def generate_can_hw_obj_filter(self):
        self.generate_bcan_hw_obj_filter_by_msg()
        self.generate_ccan_hw_obj_filter_by_msg()
        self.generate_pcan_hw_obj_filter_by_msg()
        self.generate_acan_hw_obj_filter_by_msg()
        self.generate_dcan_hw_obj_filter_by_msg()
        for cntle in Controller:
            # if cntle == Controller.CanController_CAN5:
            #     continue
            print(f"{cntle.name}: hw object totle {len(self.can_hw_obj_filter[cntle])}")
            # for i in range(len(self.can_hw_obj_filter[cntle])):
            #     print(f"\{self.can_hw_obj_filter[cntle][i].id}\t{self.can_hw_obj_filter[cntle][i].code}\t{self.can_hw_obj_filter[cntle][i].mask}")
    # print can matrix by send type
    def print_can_matrix_by_send_type(self):
        cntles = list(Controller)
        for cntle in cntles[:5]:
            print(f"{cntle.name}: totle {len(self.can_send_messages[cntle])}")
            print(f"{'Msg Name':<{25}}", \
            f"{'Msg Type':<{10}}", \
            f"{'Msg ID':<{15}}", \
            f"{'Send Type':<{10}}", \
            f"{'Cycle Time':<{10}}", \
            f"{'Len':<{5}}", \
            f"{'Dir':<{5}}")
            for content in self.can_send_messages[cntle]:
                # if "S" == content.direction:
                #     continue
                print(f"{'' if content.msg_name is None else content.msg_name:<{25}}", \
                    f"{'' if content.msg_type is None else content.msg_type:<{10}}", \
                    f"{'' if content.msg_id is None else content.msg_id:<{15}}", \
                    f"{'' if content.send_type is None else content.send_type:<{10}}", \
                    f"{'' if content.cycle_time is None else content.cycle_time:<{10}}", \
                    f"{'' if content.msg_length is None else content.msg_length:<{5}}", \
                    f"{'' if content.direction is None else content.direction:<{5}}")

if __name__ == "__main__":
    all_can_messages = gl0906_can_parse()
    all_can_messages.find_and_parse_can_comm_matrix_file()
    all_can_messages.find_and_parse_can_pdur_table()
    all_can_messages.print_can_matrix_by_send_type()
    all_can_messages.generate_can_hw_obj_filter()